package com.lazi.june.gateway.filter;

import com.lazi.june.common.base.RequestConst;
import com.lazi.june.gateway.config.GatewayProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.InetSocketAddress;


/**
 * 限流过滤器
 * <p>
 * 基于 Redisson 的分布式限流过滤器，使用令牌桶算法防止恶意请求或流量激增导致的系统过载。
 * 每个客户端（通过 IP 地址识别）都有独立的限流器。
 * <p>
 * 令牌桶算法原理：
 * 1. 每个客户端有一个令牌桶，桶有固定容量
 * 2. 令牌以恒定速率生成并放入桶中（例如每秒生成 50 个令牌）
 * 3. 桶满时，新生成的令牌会被丢弃
 * 4. 每个请求需要消耗 1 个令牌，令牌不足时请求被拒绝
 * 5. 令牌桶允许一定程度的突发流量（桶的容量）
 * <p>
 * Redisson 优势：
 * - 分布式限流：多个网关实例共享限流状态
 * - 原子操作：线程安全和分布式一致性
 * - 自动过期：无需手动清理过期数据
 * <p>
 * 限流策略：
 * - 基于客户端 IP 地址进行独立限流
 * - 优先使用 X-Forwarded-For 请求头（支持负载均衡、CDN 场景）
 * - 令牌桶容量可配置（默认等于每秒限流速率）
 * <p>
 * 配置示例：
 * <pre>
 * june:
 *   gateway:
 *     rate-limit:
 *       enabled: true
 *       per-second: 50.0    # 每秒 50 个请求
 *       capacity: 100.0     # 桶容量 100（可选，默认等于 per-second）
 * </pre>
 * <p>
 * 限流响应：
 * - HTTP 状态码：429 Too Many Requests
 * - 响应头：Retry-After: 1（建议 1 秒后重试）
 *
 * @author huliqian
 * @since 2025/8/21
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class RateLimitFilter implements GlobalFilter, Ordered {

    /**
     * Redisson 客户端（用于分布式限流）
     */
    private final RedissonClient redissonClient;

    /**
     * 网关配置属性
     */
    private final GatewayProperties props;

    /**
     * Redis Key 前缀：限流器
     */
    private static final String KEY_PREFIX_RATE_LIMITER = "gateway:ratelimit:";

    /**
     * 过滤器核心逻辑：执行限流检查
     * <p>
     * 执行流程：
     * 1. 检查是否启用限流
     * 2. 解析客户端标识（IP 地址）
     * 3. 获取或创建该客户端的 Redisson 限流器
     * 4. 尝试获取 1 个令牌
     * 5. 令牌不足时返回 429 Too Many Requests
     *
     * @param exchange 当前请求的 ServerWebExchange
     * @param chain    过滤器链
     * @return Mono<Void> 异步响应
     */
    @NonNull
    @Override
    public Mono<Void> filter(@NonNull ServerWebExchange exchange, @NonNull GatewayFilterChain chain) {
        // 1. 获取限流配置
        GatewayProperties.RateLimitConfig config = props.getRateLimit();
        if (!config.isEnabled() || config.getPerSecond() <= 0) {
            // 限流未启用或速率 <= 0 表示禁用限流
            return chain.filter(exchange);
        }

        // 2. 解析客户端标识（IP 地址）
        String clientKey = resolveClientKey(exchange.getRequest());

        // 3. 获取或创建该客户端的 Redisson 限流器
        RRateLimiter rateLimiter = getRateLimiter(clientKey, config);

        // 4. 尝试获取 1 个令牌
        if (!rateLimiter.tryAcquire(1)) {
            // 令牌不足，触发限流
            log.warn("[RateLimit] Client {} exceeded rate limit", clientKey);
            exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
            // 建议客户端 1 秒后重试
            exchange.getResponse().getHeaders().add("Retry-After", "1");
            return exchange.getResponse().setComplete();
        }

        // 5. 令牌获取成功，继续处理请求
        return chain.filter(exchange);
    }

    /**
     * 获取或创建 Redisson 限流器
     * <p>
     * 使用 Redisson 的 RRateLimiter 实现分布式限流
     *
     * @param clientKey 客户端标识
     * @param config    限流配置
     * @return RRateLimiter 限流器实例
     */
    private RRateLimiter getRateLimiter(String clientKey, GatewayProperties.RateLimitConfig config) {
        String key = KEY_PREFIX_RATE_LIMITER + clientKey;
        RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);

        // 检查限流器是否已配置
        if (!rateLimiter.isExists()) {
            // 计算桶容量：如果配置了 capacity 则使用配置值，否则使用 perSecond
            long capacity = config.getCapacity() != null
                    ? config.getCapacity().longValue()
                    : (long) Math.max(1, config.getPerSecond());

            // 配置限流器：令牌生成速率和桶容量
            // RateType.OVERALL 表示所有客户端共享配置（但每个 IP 独立计数）
            rateLimiter.trySetRate(RateType.OVERALL, (long) config.getPerSecond(), 1, RateIntervalUnit.SECONDS);
        }

        return rateLimiter;
    }

    /**
     * 解析客户端标识（IP 地址）
     * <p>
     * 识别优先级：
     * 1. 优先使用 X-Forwarded-For 请求头（适用于负载均衡、CDN、反向代理场景）
     * 2. 其次使用请求的远程 IP 地址
     * 3. 无法获取时返回 "unknown"
     * <p>
     * X-Forwarded-For 格式：client, proxy1, proxy2
     * 取第一个 IP 地址作为真实客户端 IP
     *
     * @param request 当前 HTTP 请求
     * @return 客户端标识字符串（通常是 IP 地址）
     */
    private String resolveClientKey(ServerHttpRequest request) {
        // 1. 优先使用 X-Forwarded-For 请求头
        String xff = request.getHeaders().getFirst(RequestConst.FORWARDED_FOR);
        if (xff != null && !xff.isBlank()) {
            // X-Forwarded-For 可能包含多个 IP，取第一个（真实客户端 IP）
            return xff.split(",")[0].trim();
        }

        // 2. 使用请求的远程 IP 地址
        InetSocketAddress addr = request.getRemoteAddress();
        return addr == null ? "unknown" : addr.getAddress().getHostAddress();
    }

    /**
     * 过滤器执行顺序
     * <p>
     * 返回 -88，在请求体缓存之后、日志记录之前执行
     * <p>
     * 执行顺序参考：
     * - CorsFilter: -200 (CORS 跨域)
     * - BlacklistFilter: -110 (黑名单检查)
     * - JwtAuthFilter: -100 (认证)
     * - RequestBodyCacheFilter: -90 (请求体缓存)
     * - RateLimitFilter: -88 (限流)
     * - RequestLoggingFilter: -80 (日志)
     *
     * @return 顺序值，越小越早执行
     */
    @Override
    public int getOrder() {
        // 在请求体缓存之后、日志记录之前执行
        return -88;
    }
}